package com.jxtc.enterprise.tenant.controller;


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jxtc.enterprise.common.aspectj.TokenRequired;
import com.jxtc.enterprise.common.constants.FoodMenuMakerEnum;
import com.jxtc.enterprise.common.constants.FoodMenuStatus;
import com.jxtc.enterprise.common.constants.MealTypeEnum;
import com.jxtc.enterprise.common.entity.*;
import com.jxtc.enterprise.common.mapper.*;
import com.jxtc.enterprise.common.service.impl.FoodMenuServiceImpl;
import com.jxtc.enterprise.common.service.impl.MealStoreConfigServiceImpl;
import com.jxtc.enterprise.common.utils.Result;
import com.jxtc.enterprise.common.utils.ResultCode;
import com.jxtc.enterprise.common.utils.ResultGenerator;
import com.jxtc.enterprise.tenant.dto.TenantStoreConfigForOneMealOfCompanyDTO;
import com.jxtc.enterprise.tenant.service.TenantCompanyMenuService;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 配置菜单页面 的接口
 * 为公司配置菜单
 */
@RestController
@RequestMapping("/jxtc/enterprise/tenant/companyMenuManage")
@Slf4j
@TokenRequired(role = TokenRequired.ROLE_TENANT)
public class TenantCompanyMenuController {


    @Autowired
    private FoodMenuMapper foodMenuMapper;

    @Autowired
    private CompanyMapper companyMapper;

    @Autowired
    private StoreMapper storeMapper;

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;

    @Autowired
    private FoodMenuServiceImpl foodMenuServiceImpl;

    @Autowired
    private FoodMapper foodMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MenuStoreConfigMapper menuStoreConfigMapper;

    @Autowired
    private TenantCompanyMenuService tenantCompanyMenuService;

    @Autowired
    private MealStoreConfigServiceImpl mealStoreConfigService;

    /**
     * 返回所有公司的基本信息，包括 停止供餐的公司，停止供餐仅表示当前不与该公司合作，根据以往的业务经验；
     * 很多公司会定期更换供餐团队，因此暂时停止供餐的公司，几个月可能会继续供餐
     * <p>
     * 后期如果合作公司较多后，此接口返回的数据会较多，可以再修改接口实现逻辑，仅返回正常合作 和 有潜力的 停止供餐的公司信息
     */
    @Operation(summary = "2.3.1 租户分页查询已经配置了菜单的公司列表, 0 表示启用，1 表示停用，-1 表示全部状态的菜单", tags = {"2.3 租户配置菜单"}, operationId = "2.3.1")
    @GetMapping("/queryAllCompanyBriefInfosByPage")
    Result<?> queryAllCompanyBriefInfosByPage(
            @RequestParam(value = "companyName", required = false, defaultValue = "") String companyName,
            @RequestParam(value = "menuStatus", required = false, defaultValue = "-1") Integer menuStatus,
            @RequestParam(value = "pageNo", required = false, defaultValue = "1") Integer pageNo,
            @RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize,
            HttpServletRequest request) {

        String requestId = UUID.randomUUID().toString();
        String tenantId = (String) request.getAttribute("userId");
        log.info("/queryAllCompanyBriefInfosByPage, requestId: {}, tenantId: {}, companyName: {}, menuStatus: {}, pageNo: {}, pageSize: {}",
                requestId, tenantId, companyName, menuStatus, pageNo, pageSize);

        List<JSONObject> formattedCompanyMenus = new ArrayList<>();
        // 查询某个租户合作的公司的菜单，租户会为自己合作的公司创建多个菜单（实际情况是，当前每个公司只会使用一个菜单），查询租户为某个公司创建的菜单列表
        List<HashMap<String, Object>> list = foodMenuMapper.queryCompanyMenusByPage(tenantId, companyName, menuStatus, (pageNo - 1) * pageSize, pageSize);
        list.forEach(foodMenu -> {

            JSONObject formattedCompanyMenu = new JSONObject();
            formattedCompanyMenu.put("menuId", foodMenu.get("menuId"));
            formattedCompanyMenu.put("companyId", foodMenu.get("companyId"));
            formattedCompanyMenu.put("companyName", foodMenu.get("companyName"));
            formattedCompanyMenu.put("phoneNumber", foodMenu.get("phoneNumber"));
            formattedCompanyMenu.put("hrName", foodMenu.get("hrName"));
            LocalDateTime startSupplyDateTime = (LocalDateTime) foodMenu.get("commencementDate");
            String formattedTime = Optional.of(startSupplyDateTime).map(time -> time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).orElse("");
            formattedCompanyMenu.put("startSupplyDateTime", formattedTime);
            formattedCompanyMenu.put("menuStatus", foodMenu.get("menuStatus"));
            List<String> mealTypes = new ArrayList<>();
            // 当前company表中，meals_config 列保存的是json格式的字符串，包括各个用餐时段的详细配置，开始，结束时间，用餐时段名称等
            List<MealConfig> mealsConfigVos = JSONArray.parseArray((String) foodMenu.get("mealsConfig"), MealConfig.class);
            // log.info("companyId: {}, company name: {}", foodMenu.get("companyId"), foodMenu.get("companyName"));
            for (MealConfig mealConfigVo : mealsConfigVos) {
                Boolean enabled = mealConfigVo.getEnabled();
                if (enabled) {
                    // 从各个用餐时段配置VO中仅取出 mealType 用餐时段名称 返回给前端
                    mealTypes.add(mealConfigVo.getMealType());
                }
            }
            formattedCompanyMenu.put("mealTypes", mealTypes);
            formattedCompanyMenus.add(formattedCompanyMenu);
        });

        int totalRows = list.size();
        int totalPages = totalRows % pageSize == 0 ? totalRows / pageSize : totalRows / pageSize + 1;

        Result<Object> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        JSONObject resultData = new JSONObject();
        resultData.put("currentPage", pageNo);
        resultData.put("totalPages", totalPages);
        resultData.put("totalRows", totalRows);
        resultData.put("data", formattedCompanyMenus);
        result.setData(resultData);

        log.info("/queryAllCompanyBriefInfosByPage, requestId: {}, response: {}", requestId, resultData);
        return result;
    }


    @Operation(summary = "2.3.2 租户查询与其合作的公司信息列表", tags = {"2.3 租户配置菜单"}, operationId = "2.3.2")
    @GetMapping("/queryAllCompanyBriefInfos")
    Result<?> queryAllCompanyBriefInfos(HttpServletRequest request) {
        String requestId = UUID.randomUUID().toString();
        String tenantId = (String) request.getAttribute("userId");
        log.info("/queryAllCompanyBriefInfos, requestId: {}, tenantId: {}", requestId, tenantId);
        // 查询某个租户合作的公司列表
        // select id, name from company where tenant_id = #{tenantId} and del_flag = 0;
        List<Company> list = companyMapper.selectList(new LambdaQueryWrapper<Company>().eq(Company::getTenantId, tenantId).eq(Company::getDelFlag, false)
                .select(Company::getId, Company::getName));

        List<JSONObject> formatedCompanyInfos = new ArrayList<>();
        list.forEach(companyInfo -> {
            JSONObject formatedCompany = new JSONObject();
            formatedCompany.put("companyId", companyInfo.getId());
            formatedCompany.put("companyName", companyInfo.getName());
            formatedCompanyInfos.add(formatedCompany);
        });
        Result<Object> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(formatedCompanyInfos);

        log.info("/queryAllCompanyBriefInfos, response: {}", formatedCompanyInfos);
        return result;
    }


    @Operation(summary = "2.3.3 租户为合作的公司新增菜单", tags = {"2.3 租户配置菜单"}, operationId = "2.3.3")
    @PostMapping("/addNewFoodMenuForCompany")
    Result<?> addNewFoodMenuForCompany(HttpServletRequest request, @RequestBody JSONObject requestBody) {
        String requestId = UUID.randomUUID().toString();
        String tenantId = (String) request.getAttribute("userId");
        log.info("/addNewFoodMenuForCompany, requestId: {}, tenantId: {}", requestId, tenantId);

        String companyId = requestBody.getString("companyId");
        String startSupplyDateTime = requestBody.getString("startSupplyDateTime");

        FoodMenu foodMenu = new FoodMenu();
        String menuId = UUID.randomUUID().toString().replace("-", "").toLowerCase();
        foodMenu.setMenuId(menuId);
        foodMenu.setTenantId(tenantId); // 租户id
        foodMenu.setCompanyId(companyId);
        foodMenu.setCommencementDate(LocalDateTime.parse(startSupplyDateTime.replace(" ", "T"), DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        // 菜单 在food_menu 表中对应的数据行的，使用特殊的状态码进行标记
        foodMenu.setMarker(FoodMenuMakerEnum.MENU_MARKER.getCode());
        foodMenu.setStatus(FoodMenuStatus.DEACTIVE.getStatus()); // 新添加的菜单，状态为 未启用
        foodMenu.setFoodId("000000000000000000"); // 此条记录仅做标记使用，因此 店铺id，菜品id都使用特殊字符串
        foodMenu.setStoreId("000000000000000000");
        foodMenu.setSalePrice(0);
        foodMenu.setStorePrice(0);
        foodMenu.setShowPrice(0);
        foodMenuMapper.insert(foodMenu);

        Result<Object> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");

        return result;
    }


    /**
     * 展示状态为正常营业的店铺列表, 仅返回没有给某个公司的菜单挑选的店铺，因此请求参数中要去传递menuId
     */
    @Operation(summary = "2.3.4 租户查看可挑选的店铺", tags = {"2.3 租户配置菜单"}, operationId = "2.3.4")
    @GetMapping("/queryAvailableStoresWithKeyword")
    public Result<?> queryAvailableStoresWithKeyword(
            @RequestParam("menuId") String menuId,
            @RequestParam(value = "keyword", required = false) String keyword,
            @RequestParam(value = "mealType") String mealType,
            @RequestParam("pageNo") Integer pageNo,
            @RequestParam("pageSize") Integer pageSize, HttpServletRequest request) {

        String requestId = UUID.randomUUID().toString();
        String tenantId = (String) request.getAttribute("userId");
        log.info("/queryAvailableStoresWithKeyword, requestId: {}, menuId: {}, keyword: {}, mealType: {}, pageNo: {}, pageSize: {}",
                requestId, menuId, keyword, mealType, pageNo, pageSize);

        // selectObjs() 只会返回结果集中，每行的第一个字段；此处结果集中每行中只有一列，因此可以使用此方法
        // 返回的店铺id中，包含虚拟店铺id 0000000000000，并不影响结果，因为在 Store表中没有 id为 0000000000000 的记录
        // 每个菜单上已经挑选的店铺
        List<String> storeIdsOfMenu = foodMenuMapper.selectObjs(
                new LambdaQueryWrapper<FoodMenu>()
                        .eq(FoodMenu::getDelFlag, false)
                        .eq(FoodMenu::getMenuId, menuId)
                        // food_menu 表中，menu_id 相同行，有三种不同的marker，1 表示为公司配置的菜单，2表示为公司菜单挑选的店铺，3表示为公司菜单挑选的菜品
                        .eq(FoodMenu::getMarker, FoodMenuMakerEnum.STORE_MARKER.getCode())
                        // 查询出为某个时段的用餐，已经挑选的店铺,只有当某个时段配置了店铺，才不会返回；其他时段挑选了该店铺，依然可以返回，可以挑选
                        .eq(FoodMenu::getMealType, mealType)
                        .select(FoodMenu::getStoreId));
        HashSet<String> alreadyChoosedStoreIds = new HashSet<>();
        for (String storeId : storeIdsOfMenu) {
            alreadyChoosedStoreIds.add(storeId);
        }

        Page<Store> page = new Page<>(pageNo, pageSize);
        LambdaQueryWrapper<Store> queryWrapper = new LambdaQueryWrapper<>();
        if (keyword == null || keyword.isEmpty()) {
            // 当点击【新增店铺】按钮，跳转到 可供挑选的店铺页面时，不传递keyword，分页加载所有del_flag为0的店铺
            queryWrapper.eq(Store::getDelFlag, 0);
        } else {
            // 当在 可供挑选的店铺页面中，输入 店铺名搜索店铺时，传递keyword，模糊搜索符合条件的，del_flag为0的店铺
            queryWrapper.eq(Store::getDelFlag, 0).like(Store::getName, keyword);
        }
        // not in () 会导致 sql 报错，因此需要判断 alreadyChoosedStoreIds 列表是否为空
        if (alreadyChoosedStoreIds != null && !alreadyChoosedStoreIds.isEmpty()) {
            queryWrapper.notIn(Store::getId, alreadyChoosedStoreIds);
        }
        // 仅返回属于租户自己合作的店铺
        queryWrapper.eq(Store::getTenantId, tenantId);
        Page<Store> pageList = storeMapper.selectPage(page,
                queryWrapper.select(Store::getId, Store::getName, Store::getCategoryName,
                        Store::getStorePrice, Store::getAddress));

        JSONObject resultData = new JSONObject();
        JSONArray storeList = new JSONArray();
        for (int k = 0; k < pageList.getRecords().size(); k++) {
            Store oneStoreRepository = pageList.getRecords().get(k);
            JSONObject formatedStoreInfo = new JSONObject();
            formatedStoreInfo.put("storeId", oneStoreRepository.getId());
            formatedStoreInfo.put("storeName", oneStoreRepository.getName());
            formatedStoreInfo.put("categoryName", oneStoreRepository.getCategoryName());
            formatedStoreInfo.put("storePrice", oneStoreRepository.getStorePrice());
            formatedStoreInfo.put("storeAddress", oneStoreRepository.getAddress());
            storeList.add(formatedStoreInfo);
        }

        resultData.put("availableStores", storeList);
        resultData.put("totalPages", pageList.getPages());
        resultData.put("totalCount", pageList.getTotal());
        resultData.put("currentPage", pageList.getCurrent());

        Result<Object> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(resultData);
        log.info("/queryAvailableStoresWithKeyword, requestId: {}, response successfully", requestId);
        return result;
    }


    /**
     * 为公司菜单添加店铺，可以一次添加一个或多个店铺，传递的参数格式为
     * [
     * ["store_id_1111", "华南小碗菜-南油"],
     * ["store_id_1112", "华南小碗菜-福田"]
     * ]
     */
    @Operation(summary = "2.3.5 租户为公司某个时段的用餐挑选店铺", tags = {"2.3 租户配置菜单"}, operationId = "2.3.5")
    @PostMapping("/addStoresForCompanyFoodMenu")
    public Result<?> addStoresForCompanyFoodMenu(
            @RequestBody() JSONObject storesForCompanyMenu, HttpServletRequest request) {

        String requestId = UUID.randomUUID().toString();
        String tenantId = (String) request.getAttribute("userId");
        log.info("/addStoresForCompanyFoodMenu, requestId: {}, 为公司的某个菜单挑选一个或多个店铺, request body: {}, tenantId: {}",
                requestId, storesForCompanyMenu, tenantId);
        Result<String> result = new Result<>();

        String menuId = storesForCompanyMenu.getString("menuId");
        String companyId = storesForCompanyMenu.getString("companyId");
        // 多餐功能：某个时段，比如 lunch午餐，dinner晚餐 等
        String mealType = storesForCompanyMenu.getString("mealType");
        List<String> selectedStoreIds = storesForCompanyMenu.getJSONArray("storeIds").toJavaList(String.class);
        List<String> alreadySelectedStoreIds = foodMenuMapper.selectObjs(
                new LambdaQueryWrapper<FoodMenu>()
                        .eq(FoodMenu::getDelFlag, false)
                        .eq(FoodMenu::getMenuId, menuId)
                        .eq(FoodMenu::getMealType, mealType)
                        .in(FoodMenu::getStoreId, selectedStoreIds)
                        .select(FoodMenu::getStoreId));

        if (alreadySelectedStoreIds != null && !alreadySelectedStoreIds.isEmpty()) {
            result.setCode(ResultCode.INTERNAL_SERVER_ERROR);

            String errMsg = MessageFormat.format("已经为菜单: {0}  配置了店铺: {1}， 请求联系后台技术人员排查问题", menuId, alreadySelectedStoreIds);
            result.setMessage("error");
            result.setData(errMsg);
            log.error("/addStoresForCompanyFoodMenu, requestId: {}, error message: {}", requestId, errMsg);
            return result;
        }

        // 查询出店铺所属菜单项的状态，将店铺添加到菜单上时，店铺的状态应该和 菜单状态 保存一致
        Integer menuStatus = fetchMenuStatusByMenuId(menuId);

        List<FoodMenu> siteStoreList = new ArrayList<>();
        // 10-30 基本半导体，晚餐菜单中，蒙自源米线|G，店铺id: 9c80b353db89cfbf8c19f5ff8dfa69d4, 重复记录导致从菜单删除该店铺时，先查询时，selectOne() 返回2条，导致sql异常，导致删除失败
        // food_menu表中的 两条记录，id分别为：9a6422e78c5839d90c05701da6b732f2，e602a19b10b2a02210c81034352f02a3 的 menu_id和store_id 都相同，
        // 且创建时间，最近更新时间完全一样，显然是 向food_menu表中添加店铺时，同一个店铺添加了2次，分析代码，可能是此处的 selectedStoreIds 存在重复的店铺id导致的
        // 此处添加去重逻辑，避免出现重复记录
        List<String> uniqueStoreIds = selectedStoreIds.stream().distinct().collect(Collectors.toList());
        for (String storeId : uniqueStoreIds) {
            FoodMenu foodMenu = new FoodMenu();
            foodMenu.setCompanyId(companyId);
            foodMenu.setMenuId(menuId);
            foodMenu.setTenantId(tenantId); // 租户id，用于标明添加的店铺是为那个租户合作的公司的菜单的某个用餐时段添加的
            // 店铺 在food_menu 表中对应的数据行的 此字段，没有意义，此处赋值只是避免insert时报错
            foodMenu.setCommencementDate(LocalDateTime.now());
            foodMenu.setMarker(FoodMenuMakerEnum.STORE_MARKER.getCode());
            foodMenu.setStatus(menuStatus); // 将店铺添加到菜单上时，店铺的状态应该和 菜单状态 保存一致
            foodMenu.setStoreId(storeId);
            foodMenu.setFoodId("");
            foodMenu.setSalePrice(0);
            foodMenu.setStorePrice(0);
            foodMenu.setShowPrice(0);
            foodMenu.setLimitCount(0);
            // 多餐功能：为公司菜单挑选新店铺时，设置用餐时段
            foodMenu.setMealType(mealType);
            foodMenu.setDelFlag(false);
            foodMenu.setCreateTime(LocalDateTime.now());
            foodMenu.setUpdateTime(LocalDateTime.now());

            siteStoreList.add(foodMenu);
        }

        boolean ret = foodMenuServiceImpl.saveBatch(siteStoreList);
        if (ret) {
            String msg = MessageFormat.format("为公司Id: {0} 的菜单Id: {1} 配置 {2} 个新店铺成功", companyId, menuId, siteStoreList.size());
            log.info("/addStoresForCompanyFoodMenu, requestId: {}, response: {}", requestId, msg);

            List<MenuStoreConfig> menuStoreConfigs = tenantCompanyMenuService.buildMenuStoreConfigVoForNewAddStore(companyId, mealType, siteStoreList);
            boolean ret2 = mealStoreConfigService.saveBatch(menuStoreConfigs);
            log.info("/addStoresForCompanyFoodMenu, requestId: {}, 为新挑选的每个店铺在 meal_store_config表新增一条针对用餐时段: {} 的配置项，各个时间配置值使用公司: {} 对各个用餐时段的默认配置, 批量添加结果: {}",
                    requestId, mealType, companyId, ret2);
            result.setCode(ResultCode.SUCCESS);
            result.setMessage("success");
            result.setData(msg);
        } else {
            result.setCode(ResultCode.INTERNAL_SERVER_ERROR);
            result.setMessage("failed");
            result.setData("批量为站点添加供餐店铺失败，联系后台技术人员进行处理");
            log.error("/addStoresForCompanyFoodMenu, requestId: {}, error, 批量为站点添加供餐店铺失败，联系后台技术人员进行处理", requestId);
        }

        return result;
    }


    /**
     * @param companyId 公司id
     * @param menuId    菜单id
     * @param mealType  菜单中，某个用餐时段
     */
    @Operation(summary = "2.3.6 查看公司的某个菜单详情(为公司菜单挑选的店铺和菜品))", tags = {"2.3 租户配置菜单"}, operationId = "2.3.6")
    @GetMapping("/queryMenuDetailForCompany")
    public Result<?> queryMenuDetailForCompany(
            @RequestParam("companyId") String companyId, @RequestParam("menuId") String menuId, @RequestParam("mealType") String mealType) {

        String requestId = UUID.randomUUID().toString();
        log.info("requestId: {}, /companyMenuManage/queryMenuDetailForCompany, request params, companyId: {}, menuId: {}, mealType: {}", requestId, companyId, menuId, mealType);
        List<FoodMenu> storeIdsOfCompanyMenu = foodMenuMapper.selectList(
                new LambdaQueryWrapper<FoodMenu>()
                        .eq(FoodMenu::getMenuId, menuId)
                        .eq(FoodMenu::getMealType, mealType)
                        .eq(FoodMenu::getDelFlag, false));
        if (CollectionUtils.isEmpty(storeIdsOfCompanyMenu)) {
            String msg = MessageFormat.format("公司 {0} ,用餐时段: {1} , 没有配置店铺", companyId, mealType);
            log.error("requestId: {}, /companyMenuManage/queryMenuDetailForCompany, error, response: {}", requestId, msg);
            Result<Object> result = new Result<>();
            result.setCode(ResultCode.SUCCESS);
            result.setMessage(msg);
            result.setData(null);
            return result;
        }
        Map<String, List<FoodMenu>> storesAndFoodsMap = storeIdsOfCompanyMenu.stream().collect(Collectors.groupingBy(FoodMenu::getStoreId));

        // storeIdsOfCompanyMenu 是某个菜单上的所有菜单项，包括 marker为1，菜单自身；marker为2，为公司挑选的店铺；marker为3，为公司挑选的菜品；此处仅获取为公司配置的店铺
        // 将marker为2的FoodMenu 列表，聚合成map，key为店铺id，value为 店铺对应的FoodMenu 数据项
        Map<String, FoodMenu> storeIdAndFoodMenuMap = storeIdsOfCompanyMenu.stream()
                .filter(menuItem -> menuItem.getMarker().equals(FoodMenuMakerEnum.STORE_MARKER.getCode()))
                // 理论上不会出现 menu_id, store_id 相同，marker为2的多条记录，如果出现，则挑选一个 menu_id不为空的，后面会使用到 menu_id
                .collect(Collectors.toMap(FoodMenu::getStoreId, Function.identity(), (item1, item2) -> StringUtils.isNotEmpty(item1.getMenuId()) ? item1 : item2));
        // 菜单上配置的店铺在 food_menu表中对应的id，通过 id 到多餐功能新增的表 menu_store_config 表中，查找对应的配置项，menu_store_config 表中的menu_item_id 与 food_menu表中，marker为2的记录的 id 对应
        // 注意区分 menu_store_config 表中 menu_id和 menu_item_id 的区别，menu_item_id 是菜单中的某项，marker为2,3的行都属于菜单中的某一项；marker为1的为菜单自身；
        // menu_store_config表中的 menu_id与 food_menu表中的 menu_id对应； menu_store_config 表中的menu_item_id与 food_menu表中的 id 列对应
        List<String> menuItemIdOfStores = storeIdAndFoodMenuMap.values().stream().map(foodMenu -> foodMenu.getId()).collect(Collectors.toList());

        // 查询出各个店铺针对某个公司的菜单，供餐开始，结束，取消截止，预计送达，批量打印的时间
        List<MenuStoreConfig> menuStoreConfigs = menuStoreConfigMapper.selectList(new LambdaQueryWrapper<MenuStoreConfig>()
                .eq(MenuStoreConfig::getMenuId, menuId)
                .eq(MenuStoreConfig::getMealType, mealType)
                .in(MenuStoreConfig::getMenuItemId, menuItemIdOfStores)
                .eq(MenuStoreConfig::getDelFlag, false));
        // key为店铺id，value 为该店铺为公司的某个用餐时段的配置
        Map<String, MenuStoreConfig> storeConfigForOneMealMap = menuStoreConfigs.stream()
                .collect(Collectors.toMap(MenuStoreConfig::getStoreId, Function.identity(), (o1, o2) -> o1));

        List<JSONObject> resultList = new ArrayList<>();
        for (Map.Entry<String, List<FoodMenu>> entry : storesAndFoodsMap.entrySet()) {
            String storeId = entry.getKey();
            Store storeRepositoryDo = storeMapper.selectById(storeId);
            if (storeRepositoryDo == null) {
                log.error("requestId: {}, /companyMenuManage/queryMenuDetailForCompany, error, 无法从 店铺信息库 表中查找到id为 {} 的店铺信息，跳过此店铺", requestId, storeId);
                continue;
            }
            JSONObject formatedStore = new JSONObject();
            // 店铺公共信息，如下信息对各个站点都是一样的
            formatedStore.put("storeId", storeId);
            formatedStore.put("storeName", storeRepositoryDo.getName());
            // 某个店铺为某家公司，供应的多个时段的用餐，返回店铺为公司不同时段用餐的配置的 配置id，页面上其他接口调用时会使用到
            MenuStoreConfig mealConfigByStoreId = storeConfigForOneMealMap.get(storeId);
            if (mealConfigByStoreId == null) {
                // 为公司菜单新挑选的店铺，还未配置各个时间，此时为null
                formatedStore.put("mealConfigId", null); // 为该店铺为公司的某个用餐时段的配置的配置id，对应于 menu_store_config表中的id列
            } else {
                formatedStore.put("mealConfigId", mealConfigByStoreId.getId()); // 为该店铺为公司的某个用餐时段的配置的配置id，对应于 menu_store_config表中的id列
            }
            // 获取food_menu 中，marker为2，店铺id为某个店铺id的 配置项，用于获取 menu_id 和 id 列
            FoodMenu foodMenuForStoreInfo = storeIdAndFoodMenuMap.get(storeId);
            formatedStore.put("menuId", foodMenuForStoreInfo.getMenuId());
            formatedStore.put("menuItemId", foodMenuForStoreInfo.getId()); // 店铺在菜单表中记录的 主键id,用于和 storeMenuConfig 关联时使用
            // key为 菜品类型，value为菜品 列表
            HashMap<String, List<JSONObject>> foods = new HashMap<>();
            for (FoodMenu foodMenu : entry.getValue()) {
                Food foodRepositoryDo = foodMapper.selectById(foodMenu.getFoodId());
                if (foodRepositoryDo == null) {
                    log.error("requestId: {}, /companyMenuManage/queryMenuDetailForCompany, error, 无法从菜品信息库中查找到菜品id为 {} 的菜品, menuId: {}, companyId:{}, storeId: {}",
                            requestId, foodMenu.getFoodId(), foodMenu.getMenuId(), foodMenu.getCompanyId(), foodMenu.getStoreId());
                    continue;
                }

                if (!foods.containsKey(foodRepositoryDo.getFoodTypeName())) {
                    foods.put(foodRepositoryDo.getFoodTypeName(), new ArrayList<>());
                }

                JSONObject foodInfo = new JSONObject();
                foodInfo.put("foodMenuItemId", foodMenu.getId()); // 菜品在菜单表中的记录的组件id，全局唯一，同一个菜品被添加到同一个菜单的不同用餐时段时，此id也是不一样
                foodInfo.put("foodId", foodMenu.getFoodId());
                foodInfo.put("foodName", foodRepositoryDo.getName());
                foodInfo.put("foodImage", foodRepositoryDo.getImage());
                foodInfo.put("limitCount", foodMenu.getLimitCount());
                foodInfo.put("storePrice", foodMenu.getStorePrice());
                foodInfo.put("salePrice", foodMenu.getSalePrice());
                foodInfo.put("showPrice", foodMenu.getShowPrice());
                foodInfo.put("sort", 0);
                foods.get(foodRepositoryDo.getFoodTypeName()).add(foodInfo);
            }

            formatedStore.put("foods", foods);

            resultList.add(formatedStore);
        }

        Result<Object> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(resultList);
        log.info("requestId: {}, /companyMenuManage/queryMenuDetailForCompany, response successfully", requestId);
        return result;
    }


    @Operation(summary = "2.3.7 租户查看某个店铺中可以用来配置到菜单上的菜品", tags = {"2.3 租户配置菜单"}, operationId = "2.3.7")
    @PostMapping("/queryCanSelectedFoodsByStoreId")
    public Result<?> queryCanSelectedFoodsByStoreId(@RequestBody JSONObject requestBody) {

        String requestId = UUID.randomUUID().toString();
        log.info("requestId: {}, /queryCanSelectedFoodsByStoreId, request body: {}", requestId, requestBody);

        String companyId = requestBody.getString("companyId");
        String menuId = requestBody.getString("menuId");
        String storeId = requestBody.getString("storeId");
        String keyword = requestBody.getString("keyword");
        String mealType = requestBody.getString("mealType");
        Integer pageNo = requestBody.getInteger("pageNo");
        Integer pageSize = requestBody.getInteger("pageSize");

        Page<Food> page = new Page<>(pageNo, pageSize);
        LambdaQueryWrapper<Food> foodRepositoryLambdaQueryWrapper = new LambdaQueryWrapper<>();
        foodRepositoryLambdaQueryWrapper.eq(Food::getStoreId, storeId).eq(Food::getDelFlag, false).eq(Food::getStatus, 1);

        // 查询出某个公司的菜单中，已经从某个店铺中挑选的，某个时段的菜品列表
        List<String> alreadySelectedFoodIds = foodMenuMapper.selectObjs(
                new LambdaQueryWrapper<FoodMenu>()
                        .eq(FoodMenu::getMenuId, menuId)
                        .eq(FoodMenu::getStoreId, storeId)
                        .eq(FoodMenu::getMarker, FoodMenuMakerEnum.FOOD_MARKER.getCode())
                        .eq(FoodMenu::getMealType, mealType)
                        .eq(FoodMenu::getDelFlag, false)
                        .select(FoodMenu::getFoodId));
        // 排除已经为公司某个时段，从某个店铺中挑选的菜品
        if (alreadySelectedFoodIds != null && alreadySelectedFoodIds.size() > 0) {
            foodRepositoryLambdaQueryWrapper.notIn(Food::getId, alreadySelectedFoodIds);
        }
        // 点击【挑选菜品】按钮，弹出窗口时，搜索为空，此处没有下面的筛选条件；当在搜索框中输入菜品名时，需要添加如下筛选条件；
        // 两种场景公用同一个接口，此处的if分支用来区分两种不同场景
        if (keyword != null && !keyword.isEmpty()) {
            // 进一步添加约束条件 food_name like '%keyword%';
            foodRepositoryLambdaQueryWrapper.like(Food::getName, keyword);
        }

        Page<Food> pageList = foodMapper.selectPage(page, foodRepositoryLambdaQueryWrapper);
        List<JSONObject> formatedFoodList = new ArrayList<>();
        if (pageList != null && pageList.getRecords().size() > 0) {
            for (int i = 0; i < pageList.getRecords().size(); i++) {
                Food foodRepositoryDo = pageList.getRecords().get(i);
                JSONObject foodInfo = new JSONObject();
                // 菜品的 基本信息
                foodInfo.put("foodId", foodRepositoryDo.getId());
                foodInfo.put("foodName", foodRepositoryDo.getName());
                foodInfo.put("foodType", foodRepositoryDo.getFoodTypeName()); // 店铺内的菜品类型


                foodInfo.put("storePrice", foodRepositoryDo.getStorePrice());
                foodInfo.put("salePrice", foodRepositoryDo.getSalePrice());
                // 展示价格默认使用 salePrice
                foodInfo.put("showPrice", foodRepositoryDo.getSalePrice());
                foodInfo.put("limitCount", 999);

                formatedFoodList.add(foodInfo);
            }
        }
        HashMap<String, Object> resultData = new HashMap<>();
        resultData.put("foods", formatedFoodList);
        resultData.put("totalPages", pageList.getPages());
        resultData.put("totalCount", pageList.getTotal());
        resultData.put("currentPage", pageList.getCurrent());

        Result<HashMap<String, Object>> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(resultData);
        log.info("requestId: {}, /queryCanSelectedFoodsByStoreId, response successfully", requestId);
        return result;
    }


    @Operation(summary = "2.3.8 租户从店铺中挑选菜品添加到公司菜单上", tags = {"2.3 租户配置菜单"}, operationId = "2.3.8")
    @PostMapping("/addFoodsForCompanyFoodMenu")
    public Result<?> addFoodsForCompanyFoodMenu(
            @RequestBody() JSONObject storesForCompanyMenu) {

        Result<String> result = new Result<>();

        String menuId = storesForCompanyMenu.getString("menuId");
        String companyId = storesForCompanyMenu.getString("companyId");
        String storeId = storesForCompanyMenu.getString("storeId");
        List<String> selectedFoodIds = storesForCompanyMenu.getJSONArray("foodIds").toJavaList(String.class);
        String mealType = storesForCompanyMenu.getString("mealType");

        List<Food> foods = foodMapper.selectList(new LambdaQueryWrapper<Food>().eq(Food::getDelFlag, false).in(Food::getId, selectedFoodIds));
        Map<String, List<Food>> foodIdAndFoodVoMap = foods.stream().collect(Collectors.groupingBy(food -> food.getId()));
        List<FoodMenu> companyFoodMenuItems = new ArrayList<>();

        // 查询出店铺所属菜单项的状态，将店铺添加到菜单上时，店铺的状态应该和 菜单状态 保存一致
        Integer menuStatus = fetchMenuStatusByMenuId(menuId);

        for (String foodId : selectedFoodIds) {
            FoodMenu foodMenu = new FoodMenu();
            foodMenu.setCompanyId(companyId);
            foodMenu.setMenuId(menuId);
            foodMenu.setCommencementDate(LocalDateTime.now()); // 菜品项在food_menu 表中对应的数据行的 此字段，没有意义，此处赋值只是避免insert时报错
            foodMenu.setMarker(FoodMenuMakerEnum.FOOD_MARKER.getCode());
            foodMenu.setStatus(menuStatus); // 将菜品添加到菜单上时，菜品的状态应该和 菜单状态 保存一致
            foodMenu.setStoreId(storeId);
            foodMenu.setFoodId(foodId);
            // 多餐功能：为公司菜单挑选菜品时，每个菜品属性中添加标记，标记该菜品是配置给改公司的那个时段的用餐
            foodMenu.setMealType(mealType);
            Food foodRepository = foodIdAndFoodVoMap.get(foodId).get(0);
            foodMenu.setSalePrice(foodRepository.getSalePrice());
            foodMenu.setStorePrice(foodRepository.getStorePrice());
            // 展示价格默认使用 salePrice
            foodMenu.setShowPrice(foodRepository.getSalePrice());
            foodMenu.setLimitCount(foodRepository.getLimitCount());
            foodMenu.setDelFlag(false);
            foodMenu.setCreateTime(LocalDateTime.now());
            foodMenu.setUpdateTime(LocalDateTime.now());

            companyFoodMenuItems.add(foodMenu);
        }

        boolean ret = foodMenuServiceImpl.saveBatch(companyFoodMenuItems);
        if (ret) {
            String msg = MessageFormat.format("为公司 {0} 的菜单 {1} 添加 {2} 个菜品成功", companyId, menuId, companyFoodMenuItems.size());
            log.info(msg);
            result.setCode(ResultCode.SUCCESS);
            result.setMessage("success");
            result.setData(msg);
        } else {
            result.setCode(ResultCode.INTERNAL_SERVER_ERROR);
            result.setMessage("failed");
            result.setData("批量为站点添加供餐店铺失败，联系后台技术人员进行处理");
        }

        return result;
    }

    /**
     * @return
     */
    @Operation(summary = "2.3.9 租户移除菜单上的某个菜品", tags = {"2.3 租户配置菜单"}, operationId = "2.3.9")
    @PostMapping("/removeFoodFromCompanyMenu")
    public Result<?> removeFoodFromCompanyMenu(
            @RequestBody() JSONObject storesForCompanyMenu) {

        String requestId = UUID.randomUUID().toString();
        log.info("requestId {}, tenant view, /removeFoodFromCompanyMenu, request body: {}", requestId, storesForCompanyMenu);

        Result<String> result = new Result<>();

        String menuId = storesForCompanyMenu.getString("menuId");
        String companyId = storesForCompanyMenu.getString("companyId");
        String foodId = storesForCompanyMenu.getString("foodId");
        String mealType = storesForCompanyMenu.getString("mealType");

        int count = foodMenuMapper.update(new LambdaUpdateWrapper<FoodMenu>()
                .eq(FoodMenu::getMenuId, menuId)
                .eq(FoodMenu::getFoodId, foodId)
                .eq(FoodMenu::getMealType, mealType)
                .set(FoodMenu::getDelFlag, true)
                .set(FoodMenu::getUpdateTime, LocalDateTime.now()));
        String msg = MessageFormat.format("将菜品 {0} 从公司 {1} 的菜单 {2} 上移除, 更新记录数: {3}", foodId, companyId, menuId, count);
        log.info("requestId {}, tenant view, /removeFoodFromCompanyMenu, response successfully, msg: {}", requestId, msg);
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(msg);
        result.setData(msg);

        return result;
    }


    @Operation(summary = "2.3.10 租户移除菜单上的某个店铺及菜单上该店铺的所有菜品", tags = {"2.3 租户配置菜单"}, operationId = "2.3.10")
    @PostMapping("/removeStoreFromCompanyMenu")
    public Result<?> removeStoreFromCompanyMenu(
            @RequestBody() JSONObject storesForCompanyMenu) {

        String requestId = UUID.randomUUID().toString();
        log.info("requestId {}, tenant view, /removeStoreFromCompanyMenu, request body: {}", requestId, storesForCompanyMenu);

        Result<String> result = new Result<>();

        String menuId = storesForCompanyMenu.getString("menuId");
        String companyId = storesForCompanyMenu.getString("companyId");
        String storeId = storesForCompanyMenu.getString("storeId");
        String mealType = storesForCompanyMenu.getString("mealType");

        // 1. 当移除店铺时，先查询出要软删除的行，获取到id，后面会用到
        FoodMenu foodMenu = foodMenuMapper.selectOne(
                new LambdaQueryWrapper<FoodMenu>()
                        .eq(FoodMenu::getMenuId, menuId)
                        .eq(FoodMenu::getStoreId, storeId)
                        .eq(FoodMenu::getMealType, mealType)
                        // 查询条件中不需要加此条件，通过 menu_id，店铺id，meal_type已经可以唯一确定要删除的店铺，添加此条件便于代码阅读，明确标明查询的是 marker为2的，店铺的数据行
                        .eq(FoodMenu::getMarker, FoodMenuMakerEnum.STORE_MARKER.getCode())
                        .eq(FoodMenu::getDelFlag, 0));

        // 2. 再先软删除 food_menu 表中，该时段，该店铺的 数据行；否则前面第1步的select 将查询不到数据; 同时也会标记删除 从该店铺中天选的菜品对应的行，因为 菜单上从该店铺挑选的菜品的storeId 都是一样的，都是该店铺的，
        // update语句的 where 子句除了命中店铺对应的行，还会命中 菜品的对应的行
        FoodMenu updatedFoodMenu = new FoodMenu();
        updatedFoodMenu.setDelFlag(true);
        int count = foodMenuMapper.update(updatedFoodMenu,
                new LambdaUpdateWrapper<FoodMenu>()
                        .eq(FoodMenu::getMenuId, menuId)
                        .eq(FoodMenu::getStoreId, storeId) // 此处的店铺id除了命中店铺对应的行，还会命中 菜品的对应的行
                        .eq(FoodMenu::getMealType, mealType)); // 必须要此条件，同一个店铺可以被配置到不同的用餐时段（多餐中的多个用餐时段），没有此条件会导致误删除数据

        // 3. 软删除配置表中，某个店铺针对某个公司的配置 update menu_store_config set del_flag = 1 where menu_item_id = #{foodMenu.getId()}
        MenuStoreConfig menuStoreConfig = new MenuStoreConfig();
        menuStoreConfig.setDelFlag(true);
        // @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) 注解在执行update sql时未生效，手动设置执行update操作时，表中 update_time 列的时间
        menuStoreConfig.setUpdateTime(LocalDateTime.now());
        menuStoreConfigMapper.update(menuStoreConfig,
                new LambdaUpdateWrapper<MenuStoreConfig>()
                        .eq(MenuStoreConfig::getMenuItemId, foodMenu.getId()));
        String msg = MessageFormat.format("将店铺 {0} 从公司 {1} 的菜单 {2} 上移除，共移除 {3} 个菜品", storeId, companyId, menuId, count);
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(msg);
        result.setData(msg);
        log.info("requestId {}, tenant view, /removeStoreFromCompanyMenu, msg: {}, response successfully", requestId, msg);
        return result;
    }


    @Operation(summary = "2.3.11 租户修改添加在菜单上的菜品项的价格等信息", tags = {"2.3 租户配置菜单"}, operationId = "2.3.11")
    @PutMapping("/editFoodInfoOfCompanyMenu")
    public Result<?> editFoodInfoOfCompanyMenu(
            @RequestBody() JSONObject storesForCompanyMenu) {

        String requestId = UUID.randomUUID().toString();
        log.info("requestId {}, tenant view, /editFoodInfoOfCompanyMenu, request body: {}", requestId, storesForCompanyMenu);

        Result<String> result = new Result<>();

        // 菜品在菜单表中的记录的组件id，全局唯一，同一个菜品被添加到同一个菜单的不同用餐时段时，此id也是不一样
        String menuItemId = storesForCompanyMenu.getString("menuItemId");
        String menuId = storesForCompanyMenu.getString("menuId");
        String companyId = storesForCompanyMenu.getString("companyId");
        String foodId = storesForCompanyMenu.getString("foodId");
        String salePrice = storesForCompanyMenu.getString("salePrice");
        String storePrice = storesForCompanyMenu.getString("storePrice");
        String showPrice = storesForCompanyMenu.getString("showPrice");
        Integer limitCount = storesForCompanyMenu.getInteger("limitCount");

        BigDecimal salePriceBigDecimal = new BigDecimal(salePrice);
        BigDecimal storePriceBigDecimal = new BigDecimal(storePrice);
        BigDecimal showPriceBigDecimal = new BigDecimal(showPrice);
        BigDecimal oneHundred = new BigDecimal(100);
        Integer salePriceInFen = salePriceBigDecimal.multiply(oneHundred).intValue();
        Integer storePriceInFen = storePriceBigDecimal.multiply(oneHundred).intValue();
        Integer showPriceInFen = showPriceBigDecimal.multiply(oneHundred).intValue();

        int count = foodMenuMapper.update(
                new LambdaUpdateWrapper<FoodMenu>()
                        .eq(FoodMenu::getId, menuItemId) // 菜品在菜单表中的记录的组件id，全局唯一，同一个菜品被添加到同一个菜单的不同用餐时段时，此id也是不一样
                        .eq(FoodMenu::getMenuId, menuId)
                        .eq(FoodMenu::getFoodId, foodId)
                        .set(FoodMenu::getSalePrice, salePriceInFen)
                        .set(FoodMenu::getStorePrice, storePriceInFen)
                        .set(FoodMenu::getShowPrice, showPriceInFen)
                        .set(FoodMenu::getLimitCount, limitCount)
                        .set(FoodMenu::getUpdateTime, LocalDate.now()));

        String msg = MessageFormat.format("更新公司 {0} 的菜单 {1} 上的菜品 {2}, 记录主键id: {3} 成功，受影响的记录行数 {4}", companyId, menuId, foodId, menuItemId, count);
        log.info("requestId {}, tenant view, /editFoodInfoOfCompanyMenu, response successfully, msg: {}", requestId, msg);
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(msg);
        result.setData(msg);

        return result;
    }


    @Operation(summary = "2.3.12 租户修改公司菜单禁用/启用状态", tags = {"2.3 租户配置菜单"}, operationId = "2.3.12")
    @PutMapping("/changeMenuState")
    public Result<?> changeMenuState(@RequestBody() JSONObject storesForCompanyMenu, HttpServletRequest request) {

        String requestId = UUID.randomUUID().toString();
        String tenantId = (String) request.getAttribute("userId");
        log.info("requestId {}, tenant view, /changeMenuState, tenantId: {}, request body: {}", requestId, tenantId, storesForCompanyMenu);

        Result<String> result = new Result<>();

        String menuId = storesForCompanyMenu.getString("menuId");
        String companyId = storesForCompanyMenu.getString("companyId");
        // 请求参数 1 启用，2禁用
        Integer status = storesForCompanyMenu.getInteger("status");
        if (status.equals(FoodMenuStatus.ACTIVE.getStatus())) {
            // 如果是将一个菜单从 "禁用" 状态修改为 "启用状态"，则需要判断当前是否有已经为 "启用" 状态的菜单
            // 规则：每次最多只允许一个 "启用中" 的菜单
            LambdaQueryWrapper<FoodMenu> foodMenuCountQueryWrapper = new LambdaQueryWrapper<FoodMenu>()
                    // 正常情况下，不同租户所服务的公司，其 companyId 一定不同，其实查询条件仅使用 companyId 即可
                    // 但是为了保证数据一致性，将租户 ID 也作为查询启用菜单数量的查询条件更严谨
                    .eq(FoodMenu::getTenantId, tenantId)
                    .eq(FoodMenu::getCompanyId, companyId)
                    .eq(FoodMenu::getMarker, FoodMenuMakerEnum.MENU_MARKER.getCode())
                    .eq(FoodMenu::getStatus, FoodMenuStatus.ACTIVE.getStatus())
                    .eq(FoodMenu::getDelFlag, false);
            // 根据 companyId 查询当前租户服务的这个公司处于 "启用中" 的菜单数量
            Long foodMenuCount = foodMenuMapper.selectCount(foodMenuCountQueryWrapper);
            // 若当前有菜单 "启用中"，返回提示信息 "只能有一个启用中的菜单，若想启用此菜单，请先禁用其他状态为【启用中】的菜单"
            if (foodMenuCount > 0) {
                String errorMessage = "只能有一个启用中的菜单，若想启用此菜单，请先禁用其他状态为【启用中】的菜单";
                return ResultGenerator.genFailResult(ResultCode.FAIL, errorMessage);
            }
            // 如果当前没有 "启用中" 中的菜单，执行正常更新菜单状态的流程即可
        }

        if (status.equals(FoodMenuStatus.DEACTIVE.getStatus())) {
            // 如果是将一个菜单从 "启用" 状态修改为 "停用" 状态，则需要删除该公司下所有员工的购物车中的数据
            LambdaQueryWrapper<User> queryUserPhoneWrapper = new LambdaQueryWrapper<User>()
                    .eq(User::getCompanyId, companyId)
                    .eq(User::getDelFlag, false)
                    .select(User::getPhoneNumber);
            // 通过公司 ID 查询出用户手机号列表
            List<String> userPhoneList = userMapper.selectObjs(queryUserPhoneWrapper);
            if (!CollectionUtils.isEmpty(userPhoneList)) {
                // 根据用户手机号列表和菜单 ID 查询出用户的历史购物车数据，返回购物车 ID 列表
                LambdaQueryWrapper<ShoppingCart> shoppingCartQueryWrapper = new LambdaQueryWrapper<ShoppingCart>()
                        .in(ShoppingCart::getPhone, userPhoneList)
                        .eq(ShoppingCart::getMenuId, menuId)
                        .eq(ShoppingCart::getDelFlag, false)
                        .select(ShoppingCart::getId);
                List<String> shoppingCartIdList = shoppingCartMapper.selectObjs(shoppingCartQueryWrapper);
                log.info("requestId {}, tenant view, /changeMenuState, {} shopping cart records need to be deleted, shoppingCartIdList: {}", requestId, shoppingCartIdList.size(), shoppingCartIdList);
                if (!CollectionUtils.isEmpty(shoppingCartIdList)) {
                    // 如果历史购物车 ID 列表不为空，则将这些 ID 对应的购物车数据标记为 "已删除" 状态
                    ShoppingCart shoppingCart = new ShoppingCart();
                    shoppingCart.setDelFlag(true);

                    LambdaUpdateWrapper<ShoppingCart> shoppingCartUpdateWrapper = new LambdaUpdateWrapper<ShoppingCart>().in(ShoppingCart::getId, shoppingCartIdList);
                    int updateCount = shoppingCartMapper.update(shoppingCart, shoppingCartUpdateWrapper);
                    log.info("requestId {}, tenant view, /changeMenuState, delete shopping cart records, update count: {}", requestId, updateCount);
                }
            }
        }

        // 先将菜单表中，菜单id为 menuId的所有记录的状态进行更新;
        // 此处的sql中不需要传递 tenant_id，因为 menu_id 可以做到全局唯一，不同租户为所合作公司的菜单id不一样，同一个租户为自己某一个合作公司创建的多个菜单，menu_id 也不一样
        foodMenuMapper.update(new LambdaUpdateWrapper<FoodMenu>().eq(FoodMenu::getMenuId, menuId).set(FoodMenu::getStatus, status).set(FoodMenu::getUpdateTime, LocalDateTime.now()));
        if (status.equals(FoodMenuStatus.ACTIVE.getStatus())) {
            // 因为每个公司配置多个菜单，当启用某个菜单时，将公司员工当前使用的菜单更新为最新启用的菜单，某一时刻公司的员工只能使用一个菜单，暂时不支持公司员工使用多张不同菜单的功能
            // todo 逻辑漏洞：当前后启用多个菜单，会导致员工在点餐小程序看到菜单不同；可以考虑在 HR 员工管理页面，添加为员工配置不同的菜单的功能
            // 每个公司只能和一个租户合作，因此当菜单状态切换后，该公司员工使用的最新菜单id更新sql中，不需要传递租户id，只需要传递 公司id即可
            int updatedCount = userMapper.update(new LambdaUpdateWrapper<User>().eq(User::getCompanyId, companyId).set(User::getMenuId, menuId));
            log.warn("启用 公司 {} 的菜单 {}, 将该公司所有员工当前使用的菜单更新为 {}, 共更新 {} 条记录", companyId, menuId, menuId, updatedCount);
        }

        String msg = MessageFormat.format("更新公司 {0} 的菜单 {1} 的状态为 {2}, [1 启用，2 禁用]", companyId, menuId, status);
        log.info("requestId {}, tenant view, /changeMenuState, response: {}", requestId, msg);
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(msg);
        result.setData(msg);

        return result;
    }


    @Operation(summary = "2.3.13 租户删除公司的某个菜单", tags = {"2.3 租户配置菜单"}, operationId = "2.3.13")
    @DeleteMapping("/deleteMenu")
    public Result<?> deleteMenu(String companyId, String menuId) {

        String requestId = UUID.randomUUID().toString();
        log.info("requestId {}, tenant view, /deleteMenu, companyId: {}, menuId: {}", requestId, companyId, menuId);

        Result<String> result = new Result<>();
        int deletedMenuCount = tenantCompanyMenuService.deleteMenuAndRelatedMenuConfigs(companyId, menuId, requestId);
        String msg = MessageFormat.format("删除 {0} 的菜单 {1} 成功, 更新记录数 {2}", companyId, menuId, deletedMenuCount);
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(msg);

        return result;
    }


    @Operation(summary = "2.3.14 查询店铺针对公司某一餐的配置", tags = {"2.3 租户配置菜单"}, operationId = "2.3.14")
    @GetMapping("/queryStoreConfigForCompany")
    public Result<?> queryStoreConfigForCompany(@RequestParam("storeConfigId") String storeConfigId,
                                                @RequestParam("companyId") String companyId,
                                                @RequestParam("storeId") String storeId,
                                                @RequestParam("mealType") String mealType) {
        String requestId = UUID.randomUUID().toString();
        log.info("requestId {}, tenant view, /queryStoreConfigForCompany, storeConfigId: {}, companyId: {}, storeId: {}, mealType: {}",
                requestId, storeConfigId, companyId, storeId, mealType);
        JSONObject resultData = new JSONObject();
        if (!StringUtils.isNoneEmpty(storeConfigId)) {
            // 首次为公司菜单添加某个店铺时，店铺的用餐时间未配置，使用默认值
            MealTypeEnum mealTypeEnum;
            if (MealTypeEnum.BREAK_FAST.getMealTypeEn().equals(mealType)) {
                mealTypeEnum = MealTypeEnum.BREAK_FAST;
            } else if (MealTypeEnum.LUNCH.getMealTypeEn().equals(mealType)) {
                mealTypeEnum = MealTypeEnum.LUNCH;
            } else if (MealTypeEnum.DINNER.getMealTypeEn().equals(mealType)) {
                mealTypeEnum = MealTypeEnum.DINNER;
            } else if (MealTypeEnum.MORNING_TEA.getMealTypeEn().equals(mealType)) {
                mealTypeEnum = MealTypeEnum.MORNING_TEA;
            } else if (MealTypeEnum.AFTERNOON_TEA.getMealTypeEn().equals(mealType)) {
                mealTypeEnum = MealTypeEnum.AFTERNOON_TEA;
            } else if (MealTypeEnum.NIGHT_SNACK.getMealTypeEn().equals(mealType)) {
                mealTypeEnum = MealTypeEnum.NIGHT_SNACK;
            } else {
                mealTypeEnum = MealTypeEnum.LUNCH;
            }
            resultData.put("startTime", mealTypeEnum.getStartTime());
            resultData.put("endTime", mealTypeEnum.getEndTime());
            resultData.put("canCancelTime", mealTypeEnum.getCanCancelTime());
            resultData.put("deliveryTime", mealTypeEnum.getDeliveryTime());
            resultData.put("printType", mealTypeEnum.getPrintType());
            resultData.put("batchPrintTime", mealTypeEnum.getPrintTime());

        } else {
            MenuStoreConfig menuStoreConfig = menuStoreConfigMapper.selectById(storeConfigId);
            if (menuStoreConfig != null) {
                resultData.put("startTime", menuStoreConfig.getStartTime());
                resultData.put("endTime", menuStoreConfig.getEndTime());
                resultData.put("canCancelTime", menuStoreConfig.getCanCancelTime());
                resultData.put("deliveryTime", menuStoreConfig.getDeliveryTime());
                int printType = menuStoreConfig.getPrintType().equalsIgnoreCase("batch") ? 1 : 2;
                resultData.put("printType", printType);
                resultData.put("batchPrintTime", menuStoreConfig.getPrintTime());
            } else {
                Result<JSONObject> result = new Result<>();
                result.setCode(ResultCode.INTERNAL_SERVER_ERROR);
                result.setMessage("无法查找到该店铺针对公司的配置");
                result.setData(null);
                log.info("requestId {}, tenant view, /queryStoreConfigForCompany, 无法查找到该店铺针对公司的配置", requestId);
                return result;
            }
        }

        Result<JSONObject> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(resultData);
        log.info("requestId {}, tenant view, /queryStoreConfigForCompany, response successfully", requestId);
        return result;
    }


    /**
     * 多餐功能，为公司菜单挑选店铺后，首次配置店铺针对该公司某一餐的各种配置，比如开始，结束，取消截止时间等
     */
    @Operation(summary = "2.3.15 租户挑选店铺后新增配送配置信息 ", tags = {"13. Admin 配置菜单"}, operationId = "13.15")
    @PostMapping("/saveStoreConfigForCompany")
    public Result<?> saveStoreConfigForCompany(@RequestBody TenantStoreConfigForOneMealOfCompanyDTO dto) {

        MenuStoreConfig menuStoreConfig = new MenuStoreConfig();

        menuStoreConfig.setCompanyId(dto.getCompanyId());
        menuStoreConfig.setMealType(dto.getMealType());
        menuStoreConfig.setMenuId(dto.getMenuId());
        menuStoreConfig.setMenuItemId(dto.getMenuItemId());
        menuStoreConfig.setStoreId(dto.getStoreId());
        menuStoreConfig.setStartTime(dto.getStartTime());
        menuStoreConfig.setEndTime(dto.getEndTime());
        menuStoreConfig.setCanCancelTime(dto.getCanCancelTime());
        menuStoreConfig.setDeliveryTime(dto.getDeliveryTime());
        // 1 表示定时批量打印； 2 表示 一单一打印
        String printType = dto.getPrintType() == 1 ? "batch" : "single";
        menuStoreConfig.setPrintType(printType);
        menuStoreConfig.setPrintTime(dto.getBatchPrintTime());

        menuStoreConfigMapper.insert(menuStoreConfig);

        Result<JSONObject> result = new Result<>();
        JSONObject resultData = new JSONObject();
        resultData.put("storeConfigId", menuStoreConfig.getId());
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(resultData);
        return result;
    }

    @Operation(summary = "13.16 多餐功能，编辑为公司菜单挑选店铺，针对该公司某一餐的各种配置，比如开始，结束，取消截止时间等", tags = {"13. Admin 配置菜单"}, operationId = "13.16")
    @PostMapping("/editStoreConfigForCompany")
    public Result<?> editStoreConfigForCompany(@RequestBody TenantStoreConfigForOneMealOfCompanyDTO dto) {
        log.info("/editStoreConfigForCompany, request: {}", JSONObject.toJSONString(dto));

        if (dto.getStoreConfigId() == null || dto.getStoreConfigId().isEmpty()) {
            throw new IllegalArgumentException("店铺配置 ID 不能为空");
        }

        String storeConfigId = dto.getStoreConfigId();
        MenuStoreConfig savedMenuStoreConfig = menuStoreConfigMapper.selectById(storeConfigId);
        log.info("/editStoreConfigForCompany, the original MenuStoreConfig stored in the database: {}", JSONObject.toJSONString(savedMenuStoreConfig));

        MenuStoreConfig menuStoreConfig = new MenuStoreConfig();

        menuStoreConfig.setId(dto.getStoreConfigId());
        menuStoreConfig.setCompanyId(dto.getCompanyId());
        menuStoreConfig.setMealType(dto.getMealType());
        menuStoreConfig.setMenuId(dto.getMenuId());
        menuStoreConfig.setMenuItemId(dto.getMenuItemId());
        menuStoreConfig.setStoreId(dto.getStoreId());
        // 页面上时间控件只有 yy:mm 但是为菜单挑选店铺时，向 menu_store_config 表添加的记录，时间都是 yy:mm:ss，当菜单上部分店铺修改了各个开始，结束时间后，而其他使用的是添加到店铺时的默认时间时
        // 会导致 用户在不同店铺下单，order_payment 表中的 meal_flag 格式不同，比如 "午餐 20:00 ~ 10:30" 和 "午餐 20:00 ~ 10:30:00"，导致某公司的订单，某一天的午餐订单会被拆分成两行
        // todo order_payment表的 meal_flag 之所以是 "午餐 20:00 ~ 10:30" 格式的，是因为之前不支持多餐，只能通过 此种方式区分 多个各时段的订单，多餐功能后，可以使用 lunch， dinner 用餐时段代替
        menuStoreConfig.setStartTime(formatTimeStr(dto.getStartTime()));
        menuStoreConfig.setEndTime(formatTimeStr(dto.getEndTime()));
        // menuStoreConfig.setCanCancelTime(formatTimeStr(dto.getCanCancelTime()));
        menuStoreConfig.setDeliveryTime(formatTimeStr(dto.getDeliveryTime()));
        // 1 表示定时批量打印； 2 表示 一单一打印
        String printType = dto.getPrintType() == 1 ? "batch" : "single";
        menuStoreConfig.setPrintType(printType);
        menuStoreConfig.setPrintTime(dto.getBatchPrintTime());

        menuStoreConfigMapper.updateById(menuStoreConfig);

        Result<String> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(dto.getStoreConfigId());
        return result;
    }


    /**
     * 当时间字符串格式为 hh:mm 为，为其补充 hh:mm:00，否则返回原始字符串
     *
     * @param timeStr
     * @return
     */
    private String formatTimeStr(String timeStr) {
        String[] split = timeStr.trim().split(":");
        if (split.length == 2) {
            return timeStr += ":00";
        }
        return timeStr;
    }

    private Integer fetchMenuStatusByMenuId(String menuId) {

        Integer menuStatus = FoodMenuStatus.DEACTIVE.getStatus();
        List<FoodMenu> menuDetails = foodMenuMapper.selectList(new LambdaQueryWrapper<FoodMenu>()
                .eq(FoodMenu::getDelFlag, false)
                .eq(FoodMenu::getMarker, FoodMenuMakerEnum.MENU_MARKER.getCode())
                .eq(FoodMenu::getMenuId, menuId));

        if (CollectionUtils.isEmpty(menuDetails)) {
            log.error("/addStoresForCompanyFoodMenu ,can't find menu item from food_menu, conditions: del_flag = false, marker: MENU_MARKER, menuId: {}", menuId);
        } else {
            menuStatus = menuDetails.get(0).getStatus();
        }

        return menuStatus;
    }

}
